/*
 * Parameters.java
 *
 * Created on 7 de Julho de 2011, 17:29
 *
 */

package intopo.demonstrator.partnerclassification;

import intopo.demonstrator.Event;
import util.math.Rational;
import java.util.Vector;
import java.util.TreeSet;

/**
 * Parameters of the partner classification model.
 *
 * @author <a href="mailto:mariano.pedro@gmail.com">Pedro Mariano</a>
 *
 * @version 1.0 7 de Julho de 2011
 */
public class Parameters
	extends intopo.demonstrator.CommonParameters
{
	/**
	 * Weight values in the weight vector are represented as integers.  This
	 * attribute holds the integer that represents the maximum weight a
	 * partner can have.
	 * 
	 * In the Monte Carlo simulation we take into account the number of bits
	 * used to represent weight values.
	 */
	private short maxWeight;
	/**
	 * How many players a combination has.
	 */
	private int combinationSize;
	/**
	 * Probability that a partner behaves acceptably.  When a selected
	 * partner combination is not acceptable, it is replaced.  Its slot in
	 * the combination vector has a new partner combination.  The value
	 * should be interpreted as {@code (2^31+acceptableProbability)/2^32}
	 */
	private final Vector<CharacterisationPartnerSet> partnersDescription;
	/**
	 * The set of partners to use with these parameters.
	 */
	transient private TreeSet<Partner> partners;
	/**
	 * Create a default set of parameters.
	 */
	public Parameters ()
	{
		this (3, 4, new Rational (1), 5, 2,
			new Rational (0), 2,
			new Rational (1, 2), 2,
			new Rational (1), 2);
	}
	/**
	 * Creates a new instance of Parameters
	 */
	public Parameters (int numberBitsProbability, int poolSize, Rational delta, int numberBitsWeight, int numberPartners, Object... partners)
		throws IllegalArgumentException
	{
		super (numberBitsProbability, poolSize, delta);
		this.setNumberBitsWeight (numberBitsWeight);
		this.setCombinationSize (numberPartners);
		this.partnersDescription = new Vector<CharacterisationPartnerSet> ();
		if (partners.length % 2 == 1) {
			throw new IllegalArgumentException ("Invalid number of variable parameters, must be odd");
		}
		for (int i = 0; i < partners.length; i++) {
			Rational p;
			try {
				p = (Rational) partners [i];
			}
			catch (ClassCastException cce) {
				throw new IllegalArgumentException ("Invalid parameter type, expecting a Rational");
			}
			i++;
			int q;
			try {
				q = (Integer) partners [i];
			}
			catch (ClassCastException cce) {
				throw new IllegalArgumentException ("Invalid parameter type, expecting an Integer");
			}
			this.partnersDescription.add (new CharacterisationPartnerSet (new Event (p), q));
		}
		partners = null;
	}
	/**
	 * Return the number of bits used to represent weigths in the
	 * weight vector.
	 */
	public int getNumberBitsWeight ()
	{
		int result = 0;
		int b = this.maxWeight;
		while (b > 0) {
			result++;
			b = b >> 1;
		}
		return result;
	}
	/**
	 * Sets the number of bits used to encode weights in the
	 * weight vector.  We only store the maximum weight.  Therefore this method is equivalent to {@code setMaxWeight(2^numberBitsWeight-1)}.
	 */
	public void setNumberBitsWeight (int numberBitsWeight)
		throws IllegalArgumentException
	{
		if (numberBitsWeight < 1) {
			throw new IllegalArgumentException ("The number of bits used to encode weights in the weight vector must be strictly positive");
		}
		this.maxWeight = (short) ((1 << numberBitsWeight) - 1);
	}
	/**
	 * Sets the maximum value a partner weigth can have.
	 *
	 * <p> Weights in the weight vector are represented as
	 * integers. In the Monte Carlo simulation we take into account the
	 * number of bits used to represent weights values in the
	 * weight vector.
	 *
	 * @throws IllegalArgumentException if the value is less than {@code 1}.
	 */
	public void setMaxWeight (short maxWeight)
		throws IllegalArgumentException
	{
		if (maxWeight < 1) {
			throw new IllegalArgumentException ("The maximum weight must be represented by a positive number");
		}
		this.maxWeight = maxWeight;
	}
	public short getMaxWeight ()
	{
		return this.maxWeight;
	}
	/**
	 * Return the number of partners in each combination
	 */
	public int getCombinationSize ()
	{
		return this.combinationSize;
	}
	public void setCombinationSize (int combinationSize)
		throws IllegalArgumentException
	{
		if (combinationSize < 1) {
			throw new IllegalArgumentException ("Combination size must be strictly positive");
		}
		this.combinationSize = combinationSize;
	}
	/**
	 * Return the vector that represents the set of candidate partners.
	 */
	private CharacterisationPartnerSet getPartnersDescription (int index)
	{
		return this.partnersDescription.get (index);
	}
	/**
	 * Return the quantity of the given description of partners.
	 */
	public int getPartnersDescriptionQuantity (int index)
	{
		return this.partnersDescription.get (index).getQuantity ();
	}
	/**
	 * Return the quantity of the given description of partners.
	 */
	public Rational getPartnersDescriptionProbability (int index)
	{
		return this.partnersDescription.get (index).getBehaveAcceptably ().getProbability ();
	}
	public int getPartnersDescriptionSize ()
	{
		return this.partnersDescription.size ();
	}
	public void deletePartnersDescription (int index)
	{
		this.partnersDescription.remove (index);
		this.partners = null;
	}
	public void setPartnersDescriptionQuantity (int index, int quantity)
	{
		this.partnersDescription.get (index).setQuantity (quantity);
		this.partners = null;
	}
	public void setPartnersDescriptionProbability (int index, Rational probability)
	{
		this.partnersDescription.get (index).getBehaveAcceptably ().setProbability (probability);
		this.partners = null;
	}
	/**
	 * Adds a new partner description.
	 */
	public void addPartnersDescription (Event behaveAcceptably, int quantity)
	{
		this.partnersDescription.add (new CharacterisationPartnerSet (behaveAcceptably, quantity));
		partners = null;
	}
	public void removePartnersDescription (int index)
	{
		this.partnersDescription.remove (index);
		partners = null;
	}
	/**
	 * Return the number of partners to use in the partner classification model.
	 */
	int getNumberPartners ()
	{
		int result = 0;
		for (CharacterisationPartnerSet p : this.partnersDescription) {
			result += p.quantity;
		}
		return result;
	}
	public TreeSet<Partner> getPartners ()
	{
		if (partners == null) {
			Partner.ID_GENERATOR = 0;
			partners = new TreeSet<Partner> ();
			for (CharacterisationPartnerSet p : this.partnersDescription) {
				for (int i = 0; i < p.quantity; i++) {
					partners.add (new Partner (p.getBehaveAcceptably ()));
				}
			}
		}
		return partners;
	}
	/**
	 * Represents a set of partners with some probability to behave acceptably.
	 */
	public class CharacterisationPartnerSet
	{
		/**
		 * Partners are characterised by their probability to behave acceptably.
		 */
		final private Event behaveAcceptably;
		/**
		 * How many partners exist in this set.
		 */
		private int quantity;
		/**
		 * Construct a new set of partners with some probability to be
		 * acceptable.
		 */
		public CharacterisationPartnerSet (Event behaveAcceptably, int quantity)
		{
			this.behaveAcceptably = behaveAcceptably;
			this.setQuantity (quantity);
		}
		/**
		 * Return the number of partners with this description.
		 */
		public int getQuantity ()
		{
			return quantity;
		}
		/**
		 * Sets the size of this set of partners.
		 * @throws IllegalArgumentException if the value is not strictly
		 * positive.
		 */
		public void setQuantity (int quantity)
			throws IllegalArgumentException
		{
			if (quantity <= 0) {
				throw new IllegalArgumentException ("Partner quantity must be strictly positive");
			}
			if (this.quantity != quantity) {
				partners = null;
			}
			this.quantity = quantity;
		}
		/**
		 * Return the event <i>this set of partners behaves
		 * acceptably</i>.
		 */
		public Event getBehaveAcceptably ()
		{
			return this.behaveAcceptably;
		}
		public String toString ()
		{
			return String.format ("#%d_%s", this.quantity, this.behaveAcceptably.toString ());
		}
	}
}

// Local Variables:
// mode: java
// mode: flyspell-prog
// ispell-local-dictionary: "british"
// End:
